/*-------------------------------------------------------------------------
 *
 * xlogdesc.c
 *	  访问/transam/xlog.c 的 rmgr 描述符例程
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/access/rmgrdesc/xlogdesc.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/transam.h"
#include "access/xlog.h"
#include "access/xlog_internal.h"
#include "catalog/pg_control.h"
#include "utils/guc.h"
#include "utils/timestamp.h"

/*
 * GUC 支持
 */
const struct config_enum_entry wal_level_options[] = {
	{"minimal", WAL_LEVEL_MINIMAL, false},
	{"replica", WAL_LEVEL_REPLICA, false},
	{"archive", WAL_LEVEL_REPLICA, true},	/* 已弃用 */
	{"hot_standby", WAL_LEVEL_REPLICA, true},	/* 已弃用 */
	{"logical", WAL_LEVEL_LOGICAL, false},
	{NULL, 0, false}
};

void xlog_desc(StringInfo fc_buf, XLogReaderState *fc_record)
{
	char	   *fc_rec = XLogRecGetData(fc_record);
	uint8		fc_info = XLogRecGetInfo(fc_record) & ~XLR_INFO_MASK;

	if (fc_info == XLOG_CHECKPOINT_SHUTDOWN ||
		fc_info == XLOG_CHECKPOINT_ONLINE)
	{
		CheckPoint *fc_checkpoint = (CheckPoint *) fc_rec;

		appendStringInfo(fc_buf, "redo %X/%X; "
						 "tli %u; prev tli %u; fpw %s; xid %u:%u; oid %u; multi %u; offset %u; "
						 "oldest xid %u in DB %u; oldest multi %u in DB %u; "
						 "oldest/newest commit timestamp xid: %u/%u; "
						 "oldest running xid %u; %s",
						 LSN_FORMAT_ARGS(fc_checkpoint->redo),
						 fc_checkpoint->ThisTimeLineID,
						 fc_checkpoint->PrevTimeLineID,
						 fc_checkpoint->fullPageWrites ? "true" : "false",
						 EpochFromFullTransactionId(fc_checkpoint->nextXid),
						 XidFromFullTransactionId(fc_checkpoint->nextXid),
						 fc_checkpoint->nextOid,
						 fc_checkpoint->nextMulti,
						 fc_checkpoint->nextMultiOffset,
						 fc_checkpoint->oldestXid,
						 fc_checkpoint->oldestXidDB,
						 fc_checkpoint->oldestMulti,
						 fc_checkpoint->oldestMultiDB,
						 fc_checkpoint->oldestCommitTsXid,
						 fc_checkpoint->newestCommitTsXid,
						 fc_checkpoint->oldestActiveXid,
						 (fc_info == XLOG_CHECKPOINT_SHUTDOWN) ? "shutdown" : "online");
	}
	else if (fc_info == XLOG_NEXTOID)
	{
		Oid			fc_nextOid;

		memcpy(&fc_nextOid, fc_rec, sizeof(Oid));
		appendStringInfo(fc_buf, "%u", fc_nextOid);
	}
	else if (fc_info == XLOG_RESTORE_POINT)
	{
		xl_restore_point *fc_xlrec = (xl_restore_point *) fc_rec;

		appendStringInfoString(fc_buf, fc_xlrec->rp_name);
	}
	else if (fc_info == XLOG_FPI || fc_info == XLOG_FPI_FOR_HINT)
	{
		/* 没有进一步的信息可打印 */
	}
	else if (fc_info == XLOG_BACKUP_END)
	{
		XLogRecPtr	fc_startpoint;

		memcpy(&fc_startpoint, fc_rec, sizeof(XLogRecPtr));
		appendStringInfo(fc_buf, "%X/%X", LSN_FORMAT_ARGS(fc_startpoint));
	}
	else if (fc_info == XLOG_PARAMETER_CHANGE)
	{
		xl_parameter_change fc_xlrec;
		const char *fc_wal_level_str;
		const struct config_enum_entry *fc_entry;

		memcpy(&fc_xlrec, fc_rec, sizeof(xl_parameter_change));

		/* 查找 wal_level 的字符串表示 */
		fc_wal_level_str = "?";
		for (fc_entry = wal_level_options; fc_entry->name; fc_entry++)
		{
			if (fc_entry->val == fc_xlrec.wal_level)
			{
				fc_wal_level_str = fc_entry->name;
				break;
			}
		}

		appendStringInfo(fc_buf, "max_connections=%d max_worker_processes=%d "
						 "max_wal_senders=%d max_prepared_xacts=%d "
						 "max_locks_per_xact=%d wal_level=%s "
						 "wal_log_hints=%s track_commit_timestamp=%s",
						 fc_xlrec.MaxConnections,
						 fc_xlrec.max_worker_processes,
						 fc_xlrec.max_wal_senders,
						 fc_xlrec.max_prepared_xacts,
						 fc_xlrec.max_locks_per_xact,
						 fc_wal_level_str,
						 fc_xlrec.wal_log_hints ? "on" : "off",
						 fc_xlrec.track_commit_timestamp ? "on" : "off");
	}
	else if (fc_info == XLOG_FPW_CHANGE)
	{
		bool		fc_fpw;

		memcpy(&fc_fpw, fc_rec, sizeof(bool));
		appendStringInfoString(fc_buf, fc_fpw ? "true" : "false");
	}
	else if (fc_info == XLOG_END_OF_RECOVERY)
	{
		xl_end_of_recovery fc_xlrec;

		memcpy(&fc_xlrec, fc_rec, sizeof(xl_end_of_recovery));
		appendStringInfo(fc_buf, "tli %u; prev tli %u; time %s",
						 fc_xlrec.ThisTimeLineID, fc_xlrec.PrevTimeLineID,
						 timestamptz_to_str(fc_xlrec.end_time));
	}
	else if (fc_info == XLOG_OVERWRITE_CONTRECORD)
	{
		xl_overwrite_contrecord fc_xlrec;

		memcpy(&fc_xlrec, fc_rec, sizeof(xl_overwrite_contrecord));
		appendStringInfo(fc_buf, "lsn %X/%X; time %s",
						 LSN_FORMAT_ARGS(fc_xlrec.overwritten_lsn),
						 timestamptz_to_str(fc_xlrec.overwrite_time));
	}
}

const char * xlog_identify(uint8 fc_info)
{
	const char *fc_id = NULL;

	switch (fc_info & ~XLR_INFO_MASK)
	{
		case XLOG_CHECKPOINT_SHUTDOWN:
			fc_id = "CHECKPOINT_SHUTDOWN";
			break;
		case XLOG_CHECKPOINT_ONLINE:
			fc_id = "CHECKPOINT_ONLINE";
			break;
		case XLOG_NOOP:
			fc_id = "NOOP";
			break;
		case XLOG_NEXTOID:
			fc_id = "NEXTOID";
			break;
		case XLOG_SWITCH:
			fc_id = "SWITCH";
			break;
		case XLOG_BACKUP_END:
			fc_id = "BACKUP_END";
			break;
		case XLOG_PARAMETER_CHANGE:
			fc_id = "PARAMETER_CHANGE";
			break;
		case XLOG_RESTORE_POINT:
			fc_id = "RESTORE_POINT";
			break;
		case XLOG_FPW_CHANGE:
			fc_id = "FPW_CHANGE";
			break;
		case XLOG_END_OF_RECOVERY:
			fc_id = "END_OF_RECOVERY";
			break;
		case XLOG_OVERWRITE_CONTRECORD:
			fc_id = "OVERWRITE_CONTRECORD";
			break;
		case XLOG_FPI:
			fc_id = "FPI";
			break;
		case XLOG_FPI_FOR_HINT:
			fc_id = "FPI_FOR_HINT";
			break;
	}

	return fc_id;
}

/*
 * 返回一个字符串，提供有关 XLogRecord 中所有块的信息。
 */
void XLogRecGetBlockRefInfo(XLogReaderState *fc_record, bool fc_pretty,
					   bool fc_detailed_format, StringInfo fc_buf,
					   uint32 *fc_fpi_len)
{
	int			fc_block_id;

	Assert(fc_record != NULL);

	if (fc_detailed_format && fc_pretty)
		appendStringInfoChar(fc_buf, '\n');

	for (fc_block_id = 0; fc_block_id <= XLogRecMaxBlockId(fc_record); fc_block_id++)
	{
		RelFileNode fc_rnode;
		ForkNumber	fc_forknum;
		BlockNumber fc_blk;

		if (!XLogRecGetBlockTagExtended(fc_record, fc_block_id,
										&fc_rnode, &fc_forknum, &fc_blk, NULL))
			continue;

		if (fc_detailed_format)
		{
			/* 以详细格式获取块引用。 */

			if (fc_pretty)
				appendStringInfoChar(fc_buf, '\t');
			else if (fc_block_id > 0)
				appendStringInfoChar(fc_buf, ' ');

			appendStringInfo(fc_buf,
							 "blkref #%d: rel %u/%u/%u fork %s blk %u",
							 fc_block_id,
							 fc_rnode.spcNode, fc_rnode.dbNode, fc_rnode.relNode,
							 forkNames[fc_forknum],
							 fc_blk);

			if (XLogRecHasBlockImage(fc_record, fc_block_id))
			{
				uint8		fc_bimg_info = XLogRecGetBlock(fc_record, fc_block_id)->bimg_info;

				/* 计算记录中的 FPI 数据量。 */
				if (fc_fpi_len)
					*fc_fpi_len += XLogRecGetBlock(fc_record, fc_block_id)->bimg_len;

				if (BKPIMAGE_COMPRESSED(fc_bimg_info))
				{
					const char *fc_method;

					if ((fc_bimg_info & BKPIMAGE_COMPRESS_PGLZ) != 0)
						fc_method = "pglz";
					else if ((fc_bimg_info & BKPIMAGE_COMPRESS_LZ4) != 0)
						fc_method = "lz4";
					else if ((fc_bimg_info & BKPIMAGE_COMPRESS_ZSTD) != 0)
						fc_method = "zstd";
					else
						fc_method = "unknown";

					appendStringInfo(fc_buf,
									 " (FPW%s); hole: offset: %u, length: %u, "
									 "compression saved: %u, method: %s",
									 XLogRecBlockImageApply(fc_record, fc_block_id) ?
									 "" : " for WAL verification",
									 XLogRecGetBlock(fc_record, fc_block_id)->hole_offset,
									 XLogRecGetBlock(fc_record, fc_block_id)->hole_length,
									 BLCKSZ -
									 XLogRecGetBlock(fc_record, fc_block_id)->hole_length -
									 XLogRecGetBlock(fc_record, fc_block_id)->bimg_len,
									 fc_method);
				}
				else
				{
					appendStringInfo(fc_buf,
									 " (FPW%s); hole: offset: %u, length: %u",
									 XLogRecBlockImageApply(fc_record, fc_block_id) ?
									 "" : " for WAL verification",
									 XLogRecGetBlock(fc_record, fc_block_id)->hole_offset,
									 XLogRecGetBlock(fc_record, fc_block_id)->hole_length);
				}
			}

			if (fc_pretty)
				appendStringInfoChar(fc_buf, '\n');
		}
		else
		{
			/* 以简短格式获取块引用。 */

			if (fc_forknum != MAIN_FORKNUM)
			{
				appendStringInfo(fc_buf,
								 ", blkref #%d: rel %u/%u/%u fork %s blk %u",
								 fc_block_id,
								 fc_rnode.spcNode, fc_rnode.dbNode, fc_rnode.relNode,
								 forkNames[fc_forknum],
								 fc_blk);
			}
			else
			{
				appendStringInfo(fc_buf,
								 ", blkref #%d: rel %u/%u/%u blk %u",
								 fc_block_id,
								 fc_rnode.spcNode, fc_rnode.dbNode, fc_rnode.relNode,
								 fc_blk);
			}

			if (XLogRecHasBlockImage(fc_record, fc_block_id))
			{
				/* 计算记录中的 FPI 数据量。 */
				if (fc_fpi_len)
					*fc_fpi_len += XLogRecGetBlock(fc_record, fc_block_id)->bimg_len;

				if (XLogRecBlockImageApply(fc_record, fc_block_id))
					appendStringInfo(fc_buf, " FPW");
				else
					appendStringInfo(fc_buf, " FPW for WAL verification");
			}
		}
	}

	if (!fc_detailed_format && fc_pretty)
		appendStringInfoChar(fc_buf, '\n');
}
